/*
* Can this change be committed to Googele Sites?

 Copyright (c) 2010 The Turing Project
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* Except as contained in this notice, the name(s) of the above
* copyright holders shall not be used in advertising or otherwise to
* promote the sale, use or other dealings in this Software without prior
* written authorization.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
 */

package simulator;
import java.lang.Math.*;


public class Construct{

    private static int _maxNodes = 1000;
    private static int _maxEdges = 10000;

    private int _nNodes = 0;
    private int _nEdges = 0;

    protected Node _nodes[] = new Node[_maxNodes];
    protected Edge _edges[] = new Edge[_maxEdges];

    private boolean _linkedNodes[][] = new boolean[1000][1000];

    private boolean _static;
    //Node _nodes[] = new Node[10];

    /**
     * Creates a new Construct object.
     */
    public Construct() {
        _nNodes = 0;
        _nEdges = 0;
        _static = false;

    }

    public void setStatic(Boolean value) {
        _static = value;
        for (int i = 0; i < _nNodes; i ++) {
            _nodes[i].setStatic(_static);
        }
    }
    /**
     * Adds a new node to the construct.
     * @param x x-coordinate for the node.
     * @param y y-coordinate for the node.
     * @param z z-coordinate for the node.
     */
    protected void addNode(Vector3D position, Double radius) {
        if (_nNodes < _maxNodes) {
            _nodes[_nNodes] = new Node();
            _nodes[_nNodes].setVectorPosition(position);
            _nodes[_nNodes].setScalarVelocity(0.0, 0.0, 0.0);
            _nodes[_nNodes].setMass(1.0);
            _nodes[_nNodes].setRadius(radius);
            _nodes[_nNodes].setStatic(_static);
            _nNodes += 1;
        }
    }

    /**
     * Joins two nodes with an edge.  The connecting edge holds the nodes at the
     * specified restingDistance with a force determined by the springConstant.
     * @param a
     * @param b
     * @param restingDistance
     * @param springConstant
     */
    protected void joinNodes(int nodeA, int nodeB, Double restingDistance, Double springConstant) {
        _edges[_nEdges] = new Edge();
        _edges[_nEdges].defineEdge(_nodes[nodeA], _nodes[nodeB], springConstant);
        _linkedNodes[nodeA][nodeB] = true;
        _nEdges += 1;
    }


    /**
     * Computes the distance between node a and node b.
     * @param a
     * @param b
     * @return
     */
    protected Double computeDistance (int a, int b){
        Vector3D distance = new Vector3D();
        distance.vectorSet(_nodes[a].getVectorPosition());
        distance.subtractVector(_nodes[b].getVectorPosition());
        return distance.getAmplitude();
    }

    /**
     * Links all nodes seperated by the maximumAdjacentDistance with an edge whoes
     * resting distance is equal to the distance between the nodes and spring
     * constant is specified by springConstant.
     * @param maximumAdjacentDistance
     * @param springConstant
     */
    protected void linkAdjacentNodes (Double maximumAdjacentDistance, Double springConstant) {
        for (int i = 0; i < _nNodes; i ++) {
            for (int j = 0; j < _nNodes; j ++) {
                if (i != j && computeDistance(i, j) < maximumAdjacentDistance && ! _linkedNodes[i][j]) {
                    joinNodes(i, j, computeDistance(i, j), springConstant);
                }
            }
        }
    }
    
    /**
     * Creates a 10.0 x 10.0 x 10.0 cube of nNodes randomly placed nodes.
     * @param nNodes
     */
    protected void spotNodes (int nNodes, Double radius) {
        Vector3D position = new Vector3D();
        for (int i = 0; i < nNodes; i++) {
            position.cartesianSet(Math.random()* 10.0, Math.random()* 10.0, Math.random()* 10.0);
           addNode(position, radius);
        }
    }


    public void shiftNodes (Double x, Double y, Double z) {
        Vector3D offset = new Vector3D();
        offset.cartesianSet(x, y, z);
        for (int i = 0; i < _nNodes; i ++) {
            _nodes[i].shiftPosition(offset);
        }
    }
    /**
     * This method evenly distributes the construct's nodes within
     * a 3D space defined by the exclusion function.  The boolean exculusion function
     * is redifined in subclasses to return a true value if the cartesian coordinates
     * entered as arguments fall within the defined sape.
     * @see #exclusionFunction(simulator.Vector3D)
     */
    protected void distributeNodes() {

        //Double maximumVelocity = 100.0;
        Double currentVelocity = 0.0;
        Double timeInterval = 0.0;
        Double maximumDistance = 1.0;
        
        for (int i=0; i < 1000; i++){
            timeInterval = 1.0;
            currentVelocity = 1000.0;
            while (currentVelocity * timeInterval > maximumDistance) {
                timeInterval /= 2.0;
                for (int j = 0; j < _nNodes; j++) {
                    currentVelocity = computeVelocity(j, timeInterval);
                    if (currentVelocity * timeInterval > maximumDistance)
                        break;
                }
            }
            for (int j =0; j < _nNodes; j++) {
                moveNode(j, timeInterval);
            }
        }

        for (int i = 0; i < _nNodes; i++){
            _nodes[i].setScalarVelocity(0.0, 0.0, 0.0);
        }
        
    }

    /**
     * Used by distributeNodes to compute the velocity of nodes as they are spread
     * out to fill the shape defined by exclusionFunction.
     * @param node
     * @param timeInterval
     * @return
     * @see #distributeNodes()
     */
    private Double computeVelocity (int node, Double timeInterval) {
        Vector3D vectorForce = new Vector3D();
        Double distance = 0.0;
        Double scalarForce = 0.0;

        _nodes[node].setScalarVelocity(0.0, 0.0, 0.0);
        for (int i = 0; i < _nNodes; i++) {
            if (i != node) {
                vectorForce.vectorSet(_nodes[node].getVectorPosition());
                vectorForce.subtractVector(_nodes[i].getVectorPosition());
                distance = vectorForce.getAmplitude();
                vectorForce.normalize();
                scalarForce = 1.0 / distance;
                vectorForce.multiplyBy(scalarForce);
                _nodes[node].applyPositiveForce(vectorForce, timeInterval);
            }
        }

        return _nodes[node].getScalarVelocity();
    }

    /**
     * Used by distributeNodes to move the nodes according to their computed velocities.
     * However, this does not move those nodes forward which will end outside of the region
     * defined by exclusionFunction.  Rather, it moves these nodes in the oposite
     * direction.
     * @param node
     * @param timeInterval
     * @see #distributeNodes()
     */
    private void moveNode (int node, Double timeInterval) {
        Vector3D position = new Vector3D();
        position.vectorSet(_nodes[node].getVectorPosition());
        
        if (exclusionFunction(_nodes[node].getVectorPosition())) 
            _nodes[node].applyVelocity(timeInterval);
        else
            _nodes[node].applyVelocity(-1.0 * timeInterval);
       

    }

    /**
     * This function is used by distributNodes to define the space being filled
     * by nodes.  It accepts the vector position of a test point and returns a
     * value of true if that vector is located within the defined space.  This function
     * should be redifined in subclasses to create different shapes.
     *
     * @param position
     * @return
     * @see #distributeNodes()
     */
    protected boolean exclusionFunction (Vector3D position) {
       Double x = position.getX();
       Double y = position.getY();
       Double z = position.getZ();
       Double radius = 50.0;

       return (Math.sqrt(x*x + y*y + z*z) < radius);
    }


    /**
     * Returns the number of nodes in the construct.
     * @return 
     */
    public int countNodes() {
        return _nNodes;
    }

    /**
     * Returns the number of edges in the construct.
     * @return
     */
    public int countEdges() {
        return _nEdges;
    }



    /**
     * Returns the object for the nth node.
     * @param n
     * @return
     */
    public Node fetchNode(int n) {
        return _nodes[n];
    }

    /**
     * Returns the object for the nth Edge.
     * @param n
     * @return
     */
    public Edge fetchEdge(int n) {
        return _edges[n];
    }

}
